const { Transform } = require('stream');
const cheerio = require('cheerio');
const superagent = require('superagent');
const charset = require('superagent-charset');
const request = charset(superagent);
const _request = charset(request);
const p = require('path');
const fs = require('fs-extra');
const moment = require('moment');
const _ = require('lodash');
const utils = require('../../utils');

async function download(url, path) {
  return new Promise(resolve => {
    if (!/^https?:\/\//.test(url)) {
      return resolve(url);
    }
    const extname = /png/.test(url) ? 'png' : 'jpg';
    const fullname = p.join(path, `${utils.uuid()}.${extname}`).replace(/\\/g, '/');
    _request(url).pipe(fs.createWriteStream(fullname)).on('close', function() {
      resolve(fullname);
    }).on('error', function(e) {
      resolve(url);
    });
  });
}
function hasImgChild($, parent) {
  const children = parent.children().toArray();
  if (!children.length) {
    return parent[0].name === 'img';
  }
  for (const child of children) {
    if (hasImgChild($, $(child))) {
      return true;
    }
  }
  return false;
}
async function parseChildren(list, $, parent, srcName, imagePath, seprateLevel, level=0) {
  if (!parent[0]) {
    return;
  }
  const children = parent.children().toArray();
  if (!children.length) {
    if (parent[0].name === 'img') {
      const urls = srcName.split(',').map(o=>parent[0].attribs[o]).filter(Boolean);
      let url = urls[0];
      if (url) {
        imagePath && (url = await download(url, imagePath));
        list.push({ url  });
      }
      return;
    }
    const text = parent.text().trim();
    text && list.push({ text });
    return;
  }
  if (level >= seprateLevel) {
    if (!hasImgChild($, parent)) {
      const text = parent.text().trim();
      text && list.push({ text });
      return;
    }
  }
  for (const child of children) {
    await parseChildren(list, $, $(child), srcName, imagePath, seprateLevel, level+1);
  }
}
function parse(url, selector, srcName, imagePath, seprateLevel, encoding) {
  return new Promise(resolve => {
    request.get(url).charset(encoding).end(async (err, res) => {
      if (err) {
        console.error(url, err.message);
        return resolve({ error: true });
      }
      const $ = cheerio.load(res.text);
      const list = [];
      await parseChildren(list, $, selector ? $(selector) : $.root().children(), srcName, imagePath, seprateLevel);
      resolve({ list });
    });
  });
}
async function parseHTML(html, selector, srcName, imagePath, seprateLevel) {
  const $ = cheerio.load(html);
  const list = [];
  await parseChildren(list, $, selector ? $(selector) : $.root().children(), srcName, imagePath, seprateLevel);
  return { list };
}
module.exports = function ({ encoding = 'utf-8', srcName = 'src', imagePath, inFormat, outFormat, seprateLevel=1 } = {}) {
  const stream = new Transform({ objectMode: true });
  inFormat = utils.parseFunction(inFormat);
  outFormat = utils.parseFunction(outFormat);
  const errorList = [];
  imagePath && fs.ensureDirSync(imagePath);

  stream._transform = async function (chunk, _encoding, callback) {
    utils.debug('acticleTransform [chunk]:', chunk);
    const params = inFormat ? await inFormat(chunk, { _, moment, utils }) : chunk;
    if (!params.html && !params.url) {
      throw new Error('acticleTransform必须有html或url');
    }
    let r = params.html ? await parseHTML(params.html, params.selector, srcName, imagePath, seprateLevel) : await parse(params.url, params.selector, srcName, imagePath, seprateLevel, encoding);
    if (r.error) {
      errorList.push(chunk);
      callback();
      return;
    }
    let list = r.list;
    outFormat && (list = outFormat(list, { _, moment, utils, $$: chunk }));
    stream.push(list);
    callback();
  };
  stream.on('finish', function () {
    errorList.length && console.error('acticleTransform [error]:', errorList);
    utils.debug('acticleTransform [finish]');
  });

  return stream;
};